package com.koron.mybatis;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.*;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.log4j.Logger;

/**
 * <pre>
 * 处理mybatis连数据库功能 实例使用了 {@link #getMapper(Class)}
 * {@link #getMapper(Class)} 获取mybatis的mapper对象,必须调用{@link #close()}关闭连接
 * {@link #getMapper(Class, String)} 获取mybatis的mapper对象,env为多数据库所用环境,必须调用{@link #close()}关闭连接
 * {@link #close()} 提交SQL查询并关闭数据库链接 {@link #close(boolean)}
 * 关闭数据库链接，如果参数为true则先回滚再关闭，否提交后关闭
 * {@link #runTask(SqlTask, Class)} 运行一个SQL任务(执行完任务程序自动关闭数据库连接，避免了忘了关闭数据库连接)
 * {@link #runTask(Object, String, Class, Object[])}运行一个方法，方法的第一个参数必须是{@link SessionFactory}(执行完任务程序自动关闭数据库连接，避免了忘了关闭数据库连接)
 * </pre>
 * 
 * @author swan
 * 
 */
public class SessionFactory {
	public static final String URL = "url";
	public static final String DRIVER = "driver";
	public static final String USER = "user";
	public static final String PASSWORD = "password";
	
	/**
	 * 用来存数据库链接
	 */
	private transient static HashMap<String, SqlSessionFactory> factoryMap = new HashMap<String, SqlSessionFactory>();
	/**
	 * 缓存
	 */
	private transient static HashMap<String,String> jndi = new HashMap<String,String>();
	/**
	 * 缓存
	 */
	private transient static HashMap<String,Properties> pool = new HashMap<String,Properties>();
	/**
	 * 用来存env同sqlsession的表
	 */
	private Map<String, SqlSession> envMap = new HashMap<>();

	public static Set<String> clazzSet = new TreeSet<String>(); 
	/**
	 * 获取Mapper实例
	 * 
	 * @param clazz Mapper类
	 * @param env 环境，如果为null那么使用缺省值连接
	 * @return Mapper实例
	 */
	public <T> T getMapper(Class<T> clazz, final String env) {
		String environment = env;
		if (environment == null) {// 如果没指定env,则从类声明中取出env
			EnvSource ds = clazz.getAnnotation(EnvSource.class);
			if (ds != null)
				environment = ds.value();
		}
		// 如果取不到ENV，则使用默认连接
		if (environment == null)
			environment = EnvSource.DEFAULT;
		if (!factoryMap.containsKey(environment))
			throw new RuntimeException("数据环境:" + environment + "未被注册");

		SqlSession session = envMap.get(environment);
		if (session == null) {
			if(!factoryMap.get(environment).getConfiguration().hasMapper(clazz))//如果此类未注册则自动注册
				registeClazz(environment, clazz);
			session = factoryMap.get(environment).openSession();
			envMap.put(environment, session);
		}
		
//		if(!session.getConfiguration().getMapperRegistry().hasMapper(clazz))
//			factory.getConfiguration().addMapper(clazz);
		return session.getMapper(clazz);
	}

	/**
	 * 获取Mapper
	 * 
	 * @param c 接口
	 * @return
	 */
	public <T> T getMapper(Class<T> c) {
		return getMapper(c, null);
	}

	/**
	 * 关闭所有session
	 * 
	 * @param commit 是否执行提交命令。为false执行rollback。
	 */
	public void close(boolean commit) {
		for (SqlSession session : envMap.values()) {
			if (commit)
				session.commit();
			else
				session.rollback();
			session.close();
		}
		envMap.clear();
	}

	/**
	 * 提交所有连接，并关闭所有连接。
	 */
	public void close() {
		close(true);
	}

	/**
	 * <pre>
	 * 运行SQL任务。
	 * 如果不发生异常会自动提交SQL语句，否则回滚
	 * </pre>
	 * 
	 * @param task
	 */
	@SuppressWarnings("unchecked")
	public <T> T runTask(SqlTask task, Class<T> c) {
		T t = null;
		try {
			Object o = task.run(this);
			if (o == null || c.isInstance(o)) {
				t = (T) o;
			} else {
				throw new IllegalArgumentException("返回的参数异常");
			}
			close();
		} catch (Exception ex) {
			ex.printStackTrace();
			close(false);
		}
		return t;
	}

	/**
	 * 运行SQL任务.需要配合 {@link TaskAnnotation}使用
	 * 
	 * @param object 运行方法所属的实例
	 * @param tag SQL标签
	 * @param c 运行任务要返回实例所属的类
	 * @param objs 运行方法所需的参数
	 * @return 任务运行后的对象
	 * 
	 * @see TaskAnnotation
	 */
	@SuppressWarnings("unchecked")
	public <T> T runTask(Object object, String tag, Class<T> c, Object... objs) {
		T t = null;
		for (Method method : object.getClass().getDeclaredMethods()) {
			if (method.getAnnotation(TaskAnnotation.class) != null && method.getAnnotation(TaskAnnotation.class).value().equals(tag)) {
				try {
					Object[] tmp = new Object[objs.length + 1];
					tmp[0] = this;
					System.arraycopy(objs, 0, tmp, 1, objs.length);
					method.setAccessible(true);
					Object o = method.invoke(object, tmp);
					if (o == null || c.isAssignableFrom(o.getClass())) {
						t = (T) o;
					} else {
						throw new IllegalArgumentException("返回的参数异常");
					}
					close();
				} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
					e.printStackTrace();
					close(false);
				}
				return t;
			}
		}
		return null;
	}
	
	/**
	 * 注册连接
	 * 
	 * @param env 环境名，Mybatis调用，缺省连接为 {@link EnvSource#DEFAULT}
	 * @param jndi JNDI名
	 */
	public static void registeJndiMap(String env, String jndi) {
		try {
			Context ic = new InitialContext();
			DataSource dataSource = (DataSource) ic.lookup(jndi);
			TransactionFactory transactionFactory = new JdbcTransactionFactory();
			Environment environment = new Environment(env, transactionFactory, dataSource);
			Configuration configuration = new Configuration(environment);
			factoryMap.put(env, new SqlSessionFactoryBuilder().build(configuration));
			SessionFactory.jndi.put(env, jndi);
		} catch (NamingException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 注册数据JDBC数据库连接
	 * 属性中：url为数据库连接的地址，driver为驱动,user为账号,password为密码
	 * @param env mybatis的环境
	 * @param prop 属性。
	 */
	public static void registeDBMap(String env, Properties prop) {
		String url = prop.getProperty(URL);
		String driver = prop.getProperty(DRIVER);
		DataSource dataSource = new PooledDataSource(driver, url, prop);
		TransactionFactory transactionFactory = new JdbcTransactionFactory();
		Environment environment = new Environment(env, transactionFactory, dataSource);
		Configuration configuration = new Configuration(environment);
		factoryMap.put(env, new SqlSessionFactoryBuilder().build(configuration));
		pool.put(env, prop);
	}
	/**
	 * 注册mybatis使用的类
	 * 
	 * @param env mybatis的环境
	 * @param clazz 注册的类
	 */
	protected static void registeClazz(String env, Class<?> clazz) {
		if (!factoryMap.containsKey(env))
			throw new RuntimeException("数据环境:" + env + "未被注册");
		SqlSessionFactory factory = factoryMap.get(env);
		if(!factory.getConfiguration().hasMapper(clazz))
		{
			Logger.getLogger(SessionFactory.class).info("加载类"+clazz.getName());
			factory.getConfiguration().addMapper(clazz);
		}
		clazzSet.add(clazz.getName());
	}
	/**
	 * 重新刷mybatis配置，只重新建连接，mapper不进行映射
	 */
	public static void refresh()
	{
		factoryMap=new HashMap<>();
		for (Entry<String,String> entry : jndi.entrySet()) {
			registeJndiMap(entry.getKey(), entry.getValue());
		}
		for (Entry<String,Properties> entry : pool.entrySet()) {
			registeDBMap(entry.getKey(), entry.getValue());
		}
		clazzSet.clear();
	}
	public static void clear()
	{
		factoryMap=new HashMap<>();
	}
}